package com.atguigu.gulimall.product.service.impl;

import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.TypeReference;
import com.atguigu.common.utils.PageUtils;
import com.atguigu.common.utils.Query;
import com.atguigu.gulimall.product.dao.CategoryDao;
import com.atguigu.gulimall.product.entity.AttrEntity;
import com.atguigu.gulimall.product.entity.CategoryEntity;
import com.atguigu.gulimall.product.model.vo.AttrGroupWithAttrsVO;
import com.atguigu.gulimall.product.model.vo.Catalog2VO;
import com.atguigu.gulimall.product.service.AttrService;
import com.atguigu.gulimall.product.service.CategoryBrandRelationService;
import com.atguigu.gulimall.product.service.CategoryService;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import io.micrometer.core.instrument.util.StringUtils;
import org.redisson.api.RLock;
import org.redisson.api.RReadWriteLock;
import org.redisson.api.RedissonClient;
import org.springframework.beans.BeanUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.cache.annotation.CacheEvict;
import org.springframework.cache.annotation.Cacheable;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.data.redis.core.script.DefaultRedisScript;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import java.util.*;
import java.util.concurrent.TimeUnit;
import java.util.stream.Collectors;


@Service("categoryService")
public class CategoryServiceImpl extends ServiceImpl<CategoryDao, CategoryEntity> implements CategoryService {

    @Autowired
    StringRedisTemplate stringRedisTemplate;
    @Autowired
    private RedissonClient redisson;

    @Autowired
    CategoryBrandRelationService categoryBrandRelationService;


    @Override
    public PageUtils queryPage(Map<String, Object> params) {
        IPage<CategoryEntity> page = this.page(new Query<CategoryEntity>().getPage(params), new QueryWrapper<>());
        return new PageUtils(page);
    }

    @Override
    public List<CategoryEntity> listWithTree() {
        //1、查出所有分类
        List<CategoryEntity> entities = baseMapper.selectList(null);
        //2、组装成父子树形结构
        //2.1、找到所有的一级分类
        return entities.stream()
                .filter(categoryEntity -> categoryEntity.getParentCid() == 0)
                .map(menu -> {
                    menu.setChildren(this.getChildrens(menu, entities));
                    return menu;
                })
                .sorted(Comparator.comparingInt(menu -> (menu.getSort() == null ? 0 : menu.getSort())))
                .collect(Collectors.toList());
    }

    @Override
    public void removeMenuByIds(List<Long> catIds) {
        //todo 1、检查当前删除的菜单是否被别的地方引用
        //逻辑删除
        baseMapper.deleteBatchIds(catIds);
    }

    /**
     * 找到catelogId的完整路径
     * [父,子,孙]
     *
     * @param catelogId
     * @return
     */
    @Override
    public Long[] findCatelogPath(Long catelogId) {
        List<Long> paths = new ArrayList<>();

        List<Long> parentPath = this.findParentPath(catelogId, paths);

        Collections.reverse(parentPath);

        return (Long[]) parentPath.toArray(new Long[parentPath.size()]);
    }

    /**
     * @param category
     * @CacheEvict 失效模式
     * 多个删除
     * 1、@Caching 同时操作多种
     * 2、CacheEvict的allEntries=true
     * 3、存储统一类型的数据，都可以指定成同一分区 分区名作为前缀
     */
    @Override
//    @Caching(evict = {
//            @CacheEvict(cacheNames = "category", key = "'getLevel1Category'"),
//            @CacheEvict(cacheNames = "category", key = "'getCatelogJson'")
//    })
    @CacheEvict(cacheNames = "category", allEntries = true)
    //@CachePut 方法有返回值时可以用这个注解实现双写模式
    @Transactional
    public void updateDetail(CategoryEntity category) {
        this.updateById(category);
        if (StringUtils.isNotEmpty(category.getName())) {
            categoryBrandRelationService.updateCategory(category.getCatId(), category.getName());
        }
    }

    @Autowired
    private AttrService attrService;

    @Override
    public List<AttrGroupWithAttrsVO> getAttrGroupWithAttrsByCatelogId(Long catelogId) {
        List<CategoryEntity> list = this.list(new QueryWrapper<CategoryEntity>().eq("cat_id", catelogId));
        return list.stream().map(group -> {
            AttrGroupWithAttrsVO attrsVO = new AttrGroupWithAttrsVO();
            BeanUtils.copyProperties(group, attrsVO);

            List<AttrEntity> relationAttr = attrService.getRelationAttr(attrsVO.getAttrGroupId());
            attrsVO.setAttrs(relationAttr);

            return attrsVO;
        }).collect(Collectors.toList());
    }

    /**
     * 每一个需要缓存的数据我们都来指定放到哪个名字的缓存（cacheNames）【缓存的分区(按照业务类型分)】
     * <p>
     * 当前方法的结果需要缓存，如果缓存中有结果，方法不调用
     * <p>
     * 默认行为：
     * 1、缓存命中，方法不调用
     * 2、key默认自动生成，缓存的名字：SimpleKey
     * 3、缓存的value值，使用java序列化机制
     * 4、默认过期时间-1
     * <p>
     * 自定义操作：
     * 1、指定缓存使用的key          key属性指定，接受一个SpEL
     * 2、指定缓存的过期时间          配置文件中配置，time-to-live
     * 3、将数据保存为json格式
     *
     * @return
     */
    @Override
    @Cacheable(cacheNames = "category", key = "#root.methodName", sync = true)
    public List<CategoryEntity> getLevel1Category() {
        System.out.println("getLevel1Category");
        return this.baseMapper.selectList(new QueryWrapper<CategoryEntity>().eq("parent_cid", 0));
    }

    @Override
    @Cacheable(cacheNames = "category", key = "#root.methodName", sync = true)
    public Map<Long, List<Catalog2VO>> getCatelogJson() {
        System.out.println("getCatelogJson");
        List<CategoryEntity> entityList = this.baseMapper.selectList(null);
        //查出所有的一级分类
        List<CategoryEntity> level1CategoryList = this.getChildCategoryList(entityList, 0L);
        return level1CategoryList.stream().collect(Collectors.toMap(CategoryEntity::getCatId, level1 -> {
            //查出当前一级分类的所有二级分类
            List<CategoryEntity> level2CategoryList = this.getChildCategoryList(entityList, level1.getCatId());
            return level2CategoryList.stream().map(level2 -> {
                //找当前二级分类的三级分类
                List<CategoryEntity> level3CategoryList = this.getChildCategoryList(entityList, level2.getCatId());
                List<Catalog2VO.Catalog3Vo> catalog3VoList = level3CategoryList.stream().map(level3 -> new Catalog2VO.Catalog3Vo(level2.getCatId().toString(),
                        level3.getCatId().toString(), level3.getName())).collect(Collectors.toList());
                //封装二级分类数据
                return new Catalog2VO(level1.getCatId().toString(), catalog3VoList, level2.getCatId().toString(), level2.getName());
            }).collect(Collectors.toList());
        }));
    }

    public Map<Long, List<Catalog2VO>> getCatelogJsonForRedissonReadWriteLock() {
        RReadWriteLock readWriteLock = redisson.getReadWriteLock("catalogJson-read-write-lock");
        RLock rLock = readWriteLock.readLock();
        rLock.lock(10, TimeUnit.SECONDS);
        String catalogJson = stringRedisTemplate.opsForValue().get("redis-catalogJson");
        rLock.unlock();
        if (StringUtils.isEmpty(catalogJson)) {
            return this.getCatelogJsonForDBWithRedissonLock();
        } else {
            //格式化
            return JSON.parseObject(catalogJson, new TypeReference<Map<Long, List<Catalog2VO>>>() {
            });
        }
    }

    public Map<Long, List<Catalog2VO>> getCatelogJsonForDBWithRedissonLock() {
        //占用分布式锁 注意锁的粒度
        RLock lock = redisson.getLock("catelog-json-lock");
        lock.lock(60, TimeUnit.SECONDS);
        try {
            return this.getDataFromDB();
        } finally {
            //原子删锁
            lock.unlock();
        }
    }

    public Map<Long, List<Catalog2VO>> getCatelogJsonForDBWithRedisLock() {
        String uuid = UUID.randomUUID().toString();
        //占用分布式锁
        Boolean lock = false;
        while (!lock) {
            //原子加锁成功 设置过期时间
            lock = stringRedisTemplate.opsForValue().setIfAbsent("lock", uuid, 300, TimeUnit.SECONDS);
        }
        try {
            return this.getDataFromDB();
        } finally {
            //原子删锁 lua脚本操作
            String script = "if redis.call(\"get\",KEYS[1]) == ARGV[1] then return redis.call(\"del\",KEYS[1]) else return 0 end";
            stringRedisTemplate.execute(new DefaultRedisScript<>(script, Long.class), Arrays.asList("lock"), uuid);
        }
    }

    private Map<Long, List<Catalog2VO>> getDataFromDB() {
        String catalogJson = stringRedisTemplate.opsForValue().get("redis-catalogJson");
        if (StringUtils.isNotEmpty(catalogJson)) {
            //格式化
            return JSON.parseObject(catalogJson, new TypeReference<Map<Long, List<Catalog2VO>>>() {
            });
        }

        List<CategoryEntity> entityList = this.baseMapper.selectList(null);
        //查出所有的一级分类
        List<CategoryEntity> level1CategoryList = this.getChildCategoryList(entityList, 0L);
        Map<Long, List<Catalog2VO>> map = level1CategoryList.stream().collect(Collectors.toMap(CategoryEntity::getCatId, level1 -> {
            //查出当前一级分类的所有二级分类
            List<CategoryEntity> level2CategoryList = this.getChildCategoryList(entityList, level1.getCatId());
            return level2CategoryList.stream().map(level2 -> {
                //找当前二级分类的三级分类
                List<CategoryEntity> level3CategoryList = this.getChildCategoryList(entityList, level2.getCatId());
                List<Catalog2VO.Catalog3Vo> catalog3VoList = level3CategoryList.stream().map(level3 -> new Catalog2VO.Catalog3Vo(level2.getCatId().toString(),
                        level3.getCatId().toString(), level3.getName())).collect(Collectors.toList());
                //封装二级分类数据
                return new Catalog2VO(level1.getCatId().toString(), catalog3VoList, level2.getCatId().toString(), level2.getName());
            }).collect(Collectors.toList());
        }));
        stringRedisTemplate.opsForValue().set("catalogJson", JSON.toJSONString(map), 1, TimeUnit.HOURS);
        return map;
    }


    public synchronized Map<Long, List<Catalog2VO>> getCatelogJsonForDBWithLocalLock() {
        return this.getDataFromDB();
    }

    private List<CategoryEntity> getChildCategoryList(List<CategoryEntity> entityList, Long parentCid) {
        return entityList.stream().filter(item -> item.getParentCid() == parentCid).collect(Collectors.toList());
    }

    private List<Long> findParentPath(Long catelogId, List<Long> paths) {
        paths.add(catelogId);
        CategoryEntity entity = this.getById(catelogId);
        if (entity.getParentCid() != 0) {
            this.findParentPath(entity.getParentCid(), paths);
        }
        return paths;
    }

    /**
     * 递归找到所有菜单的子菜单
     *
     * @param root      根菜单节点
     * @param allEntity 所有的菜单
     * @return
     */
    private List<CategoryEntity> getChildrens(CategoryEntity root, List<CategoryEntity> allEntity) {
        return allEntity.stream()
                .filter(categoryEntity -> Objects.equals(categoryEntity.getParentCid(), root.getCatId()))
                .map(categoryEntity -> {
                    //1、找到子菜单
                    categoryEntity.setChildren(this.getChildrens(categoryEntity, allEntity));
                    return categoryEntity;
                })
                .sorted(Comparator.comparingInt(menu -> (menu.getSort() == null ? 0 : menu.getSort())))
                .collect(Collectors.toList());

    }

}